#include "logger.hpp"

namespace my_log
{
	sync_logger::sync_logger(const std::string& name, log_level::value min_lv, flush_strategy::strategy_base strategy)
		: logger_base(name, min_lv, strategy)
	{}

	sync_logger::sync_logger(const std::string& name, log_level::value min_lv, flush_strategy::strategy_base strategy, 
		std::vector<std::string>&& format_str_arr, std::vector<std::vector<output_t>>&& output_arr)
		: logger_base(name, min_lv, strategy, std::move(format_str_arr), std::move(output_arr))
	{}

	sync_logger::~sync_logger()
	{
		_status.store(2, std::memory_order_relaxed);
	}

	void sync_logger::destroy_impl() const
	{
		delete this;
	}

	void sync_logger::logging(const log_message& msg)
	{
		landing_visitor visitor(_f);
		for (size_t i = 0; i < _format_str_arr.size(); ++i)
		{
			visitor.setString(std::vformat(_format_str_arr[i], std::make_format_args(msg)));
			visitor.setLevel(msg._level);
			for (auto& output : _outputs_arr[i])
			{
				visitor.setSelf(&output);
				std::visit(visitor, output);
			}
		}
	}

	async_logger::msg_buffer::msg_buffer(size_t len)
		: _buffer(len, '\0')
		, _level()
		, _len(0)
		, _index(0)
	{}

	async_logger::msg_buffer::msg_buffer(msg_buffer&& other) noexcept
		: _buffer(std::move(other._buffer))
		, _level(other._level)
		, _len(other._len)
		, _index(other._index)
	{}

	void async_logger::msg_buffer::write(const log_message& msg, const std::string& fmt_form, size_t index)
	{
		_level = msg._level;
		auto it = _buffer.begin();
		it = std::vformat_to(it, fmt_form, std::make_format_args(msg));
		_len = it - _buffer.begin();
		_index = index;
	}

	std::string_view async_logger::msg_buffer::read() const noexcept
	{
		return std::string_view{ _buffer.begin(), _buffer.begin() + _len };
	}

	std::string_view async_logger::msg_buffer::getLevel(int) const noexcept
	{
		return log_level::get_value(_level);
	}

	log_level::value async_logger::msg_buffer::getLevel() const noexcept
	{
		return _level;
	}

	size_t async_logger::msg_buffer::getIndex() const noexcept
	{
		return _index;
	}

	void async_logger::msg_buffer::reset() noexcept
	{
		_len = 0;
	}
	
	async_logger::msg_buffer_pool::msg_buffer_pool(size_t max_node)
		: _max_node(max_node)
		, _total_node_count(0)
		, _outter_node_count(0)
		, _node_queue(_max_node)
	{}

	async_logger::msg_buffer_pool::~msg_buffer_pool()
	{
		msg_buffer* ptr = nullptr;
		while (_node_queue.pop(ptr))
		{
			delete ptr;
		}
	}

	bool async_logger::msg_buffer_pool::allocate(std::unique_ptr<async_logger::msg_buffer>& ret_ptr)
	{
		msg_buffer* node_ptr;
		if (!_node_queue.pop(node_ptr))
		{
			if (_total_node_count == _max_node)
			{
				return false;
			}
			ret_ptr = std::make_unique<msg_buffer>();
			++_total_node_count;
			++_outter_node_count;
			return true;
		}
		ret_ptr.reset(node_ptr);
		++_outter_node_count;
		return true;
	}

	void async_logger::msg_buffer_pool::recycle(std::unique_ptr<msg_buffer>&& buf_ptr)
	{
		_node_queue.push(buf_ptr.release());
		--_outter_node_count;
	}

	bool async_logger::msg_buffer_pool::isFull() noexcept
	{
		return _total_node_count == _max_node;
	}

	size_t async_logger::msg_buffer_pool::getOutterCount() noexcept
	{
		return _outter_node_count;
	}

	async_logger::async_logger(const std::string& name, log_level::value min_lv, flush_strategy::strategy_base strategy)
		: logger_base(name, min_lv, strategy)
		, _transfer_queue(LOCK_FREE_QUEUE_SIZE)
		, _pool(strategy._printer_startup_count * 20)
		, _startup_duration(strategy._printer_startup_duration)
		, _startup_count(strategy._printer_startup_count)
		, _producer_waiter(0)
		, _consumer_cond()
		, _consumer_ctrl()
		, _worker()
	{}

	async_logger::async_logger(const std::string& name, log_level::value min_lv, flush_strategy::strategy_base strategy, 
		std::vector<std::string>&& format_str_arr, std::vector<std::vector<output_t>>&& output_arr)
		: logger_base(name, min_lv, strategy, std::move(format_str_arr), std::move(output_arr))
		, _transfer_queue(LOCK_FREE_QUEUE_SIZE)
		, _pool(strategy._printer_startup_count * 20)
		, _startup_duration(strategy._printer_startup_duration)
		, _startup_count(strategy._printer_startup_count)
		, _producer_waiter(0)
		, _consumer_ctrl()
		, _worker()
	{
		for (size_t i = 0; i < _format_str_arr.size(); ++i)
		{
			addFmtStrAndOutput_impl();
		}
	}

	async_logger::~async_logger()
	{
		_status.store(2, std::memory_order_relaxed);
		_consumer_cond.notify_all();
		_worker.join();
	}

	void async_logger::destroy_impl() const
	{
		delete this;
	}

	void async_logger::addFmtStrAndOutput_impl()
	{};

	void async_logger::start_impl()
	{
		std::function<void()> func = std::bind(&async_logger::entry, this);
		_worker = std::thread{ std::move(func) };
	}

	void async_logger::logging(const log_message& msg)
	{
		std::unique_ptr<msg_buffer> msg_node_ptr;
		for (size_t i = 0; i < _format_str_arr.size();)
		{
			if (_pool.allocate(msg_node_ptr))
			{
				msg_node_ptr->write(msg, _format_str_arr[i], i);
				_transfer_queue.push(msg_node_ptr.release());
				++i;
			}
			else
			{
				_producer_waiter.acquire();
				_producer_waiter.release();
				continue;
			}
			if (msg._level == log_level::value::FATAL || _pool.getOutterCount() >= _startup_count)
			{
				_consumer_cond.notify_all();
			}
		}
	}

	void async_logger::entry()
	{
		landing_visitor visitor(_f);
		std::unique_ptr<msg_buffer> msg_node_ptr;
		std::unique_lock thread_lock(_consumer_ctrl, std::try_to_lock_t{});
		msg_buffer* ptr = nullptr;
		bool block_notify = true, release_notify = true;
		while (_status.load(std::memory_order_relaxed) != 2 || !_transfer_queue.empty())
		{
			while (_transfer_queue.pop(ptr))
			{
				msg_node_ptr.reset(ptr);
				visitor.setString(msg_node_ptr->read());
				visitor.setLevel(msg_node_ptr->getLevel());
				for (auto& output : _outputs_arr[msg_node_ptr->getIndex()])
				{
					visitor.setSelf(&output);
					std::visit(visitor, output);
				}
				_pool.recycle(std::move(msg_node_ptr));
				if (block_notify && _pool.isFull())
				{
					_producer_waiter.acquire();
					block_notify = false, release_notify = true;
				}
				else if (release_notify && !_pool.isFull())
				{
					_producer_waiter.release();
					block_notify = true, release_notify = false;
				}
			}
			_consumer_cond.wait_for(thread_lock, _startup_duration);
		}
	}
}